#include "appender_console_color_wincolor.h"

#include "formater_pattern.h"

namespace afcore {
namespace log {

template<typename ConsoleMutex>
CAppenderWincolor<ConsoleMutex>::CAppenderWincolor(HANDLE out_handle, EAppenderColorMode mode)
  : out_handle_(out_handle)
  , mutex_(ConsoleMutex::mutex())
  , formatter_(std::make_unique<CFormaterPattern>()) {
  DWORD console_mode;
  // 检查out_handle是否指向真实的控制台
  in_console_ = ::GetConsoleMode(out_handle, &console_mode) != 0;

  SetColorMode(mode);
  colors_[kLogLevelTrace] = WHITE;
  colors_[kLogLevelDebug] = CYAN;
  colors_[kLogLevelInfo] = GREEN;
  colors_[kLogLevelWarn] = YELLOW | BOLD;
  colors_[kLogLevelError] = RED | BOLD;
  colors_[kLogLevelFatal] = BACKGROUND_RED | WHITE | BOLD;
  colors_[kLogLevelOff] = 0;
}

template<typename ConsoleMutex>
CAppenderWincolor<ConsoleMutex>::~CAppenderWincolor() {
  this->Flush();
}

template<typename ConsoleMutex>
void CAppenderWincolor<ConsoleMutex>::SetColor(ELogLevel l, WORD color) {
  std::lock_guard<RMutex> lock(mutex_);
  colors_[l] = color;
}

template<typename ConsoleMutex>
void CAppenderWincolor<ConsoleMutex>::Log(const SLogMessage &msg) {
  std::lock_guard<RMutex> lock(mutex_);
  msg.color_range_start = 0;
  msg.color_range_end = 0;
  RMemoryBuf formatted;
  formatter_->Format(msg, formatted);
  if (!in_console_) { // 如果不是控制台 写到文件中 返回
    WriteToFile(formatted);
    return;
  }

  // 是否为区间染色
  if (should_do_colors_ && msg.color_range_end > msg.color_range_start) {
    PrintRange(formatted, 0, msg.color_range_start);
    auto orig_attribs = SetForegoundColor(colors_[msg.level]);
    PrintRange(formatted, msg.color_range_start, msg.color_range_end);
    ::SetConsoleTextAttribute(out_handle_, orig_attribs);
    PrintRange(formatted, msg.color_range_end, formatted.size());
  } else {
    PrintRange(formatted, 0, formatted.size());
  }
}

template<typename ConsoleMutex>
void CAppenderWincolor<ConsoleMutex>::Flush() {
  //
}

template<typename ConsoleMutex>
void CAppenderWincolor<ConsoleMutex>::SetPattern(const std::string &pattern) {
  std::lock_guard<RMutex> lock(mutex_);
  formatter_ = std::unique_ptr<CFormatter>(new CFormaterPattern(pattern));
}

template<typename ConsoleMutex>
void CAppenderWincolor<ConsoleMutex>::SetFormatter(RFormatterUptr formatter) {
  std::lock_guard<RMutex> lock(mutex_);
  formatter_ = std::move(formatter);
}

template<typename ConsoleMutex>
void CAppenderWincolor<ConsoleMutex>::SetColorMode(EAppenderColorMode mode) {
  switch (mode) {
    case EAppenderColorMode::kAppenderColorMode_Always:
    case EAppenderColorMode::kAppenderColorMode_Automatic:
      {
        should_do_colors_ = true;
      }
      break;
    case EAppenderColorMode::kAppenderColorMode_Never:
      {
        should_do_colors_ = false;
      }
      break;
    default:
      {
        should_do_colors_ = true;
      }
      break;
  }
}

template<typename ConsoleMutex>
WORD CAppenderWincolor<ConsoleMutex>::SetForegoundColor(WORD attribs) {
  CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info;
  ::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info);
  WORD back_color = orig_buffer_info.wAttributes;
  // retrieve the current background color
  back_color &= static_cast<WORD>(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY));
  // keep the background color unchanged
  ::SetConsoleTextAttribute(out_handle_, attribs | back_color);
  return orig_buffer_info.wAttributes; // return orig attribs
}

template<typename ConsoleMutex>
void CAppenderWincolor<ConsoleMutex>::PrintRange(const RMemoryBuf &formatted, size_t start, size_t end) {
  auto size = static_cast<DWORD>(end - start);
  ::WriteConsoleA(out_handle_, formatted.data() + start, size, nullptr, nullptr);
}

template<typename ConsoleMutex>
void CAppenderWincolor<ConsoleMutex>::WriteToFile(const RMemoryBuf &formatted) {
  if (out_handle_ == nullptr) { // no console and no file redirec
    return;
  }
  auto size = static_cast<DWORD>(formatted.size());
  if (size == 0) {
    return;
  }

  DWORD total_written = 0;
  do
  {
    DWORD bytes_written = 0;
    bool ok = ::WriteFile(out_handle_, formatted.data() + total_written, size - total_written, &bytes_written, nullptr) != 0;
    if (!ok || bytes_written == 0) {
      ThrowCLogeException("wincolor_sink: write_to_file_ failed. GetLastError(): " + std::to_string(::GetLastError()));
    }
    total_written += bytes_written;
  } while (total_written < size);
}

template<typename ConsoleMutex>
CAppenderWincolorStdout<ConsoleMutex>::CAppenderWincolorStdout(EAppenderColorMode mode)
  : CAppenderWincolor<ConsoleMutex>(::GetStdHandle(STD_OUTPUT_HANDLE), mode) {
}

template<typename ConsoleMutex>
CAppenderWincolorStderr<ConsoleMutex>::CAppenderWincolorStderr(EAppenderColorMode mode)
  : CAppenderWincolor<ConsoleMutex>(::GetStdHandle(STD_ERROR_HANDLE), mode) {
}

template class CAppenderWincolor<SConsoleMutex>;
template class CAppenderWincolor<SConsoleNullMutex>;
template class CAppenderWincolorStdout<SConsoleMutex>;
template class CAppenderWincolorStdout<SConsoleNullMutex>;
template class CAppenderWincolorStderr<SConsoleMutex>;
template class CAppenderWincolorStderr<SConsoleNullMutex>;

} // !namespace log
} // !namespace afcore